﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection;
using LuaInterface;
using Microsoft.Xna.Framework;

namespace Twodi.Script
{
    /// <summary>
    /// Repositorio de Virtual Machine, Script e Funções Lua.
    /// </summary>
    public class LuaVirtualMachine
    {
        /// <summary>
        /// Virtual Machine do Lua.
        /// </summary>
        private Lua virtualMachine;

        /// <summary>
        /// Funções em C# registradas na Virtual Machine.
        /// </summary>
        private Dictionary<String, LuaFunctionAttribute> functions;

        /// <summary>
        /// Scripts guardados para execução na Virtual Machine.
        /// </summary>
        private Dictionary<String, LuaScript> scripts;

        /// <summary>
        /// Virtual Machine do Lua.
        /// </summary>
        public Lua VirtualMachine
        {
            get { return virtualMachine; }
            set { virtualMachine = value; }
        }

        /// <summary>
        /// Funções registradas.
        /// </summary>
        public Dictionary<String, LuaFunctionAttribute> Functions
        {
            get { return functions; }
            set { functions = value; }
        }

        /// <summary>
        /// Scripts guardados na Virtual Machine.
        /// </summary>
        public Dictionary<String, LuaScript> Scripts
        {
            get { return scripts; }
            set { scripts = value; }
        }

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        public LuaVirtualMachine()
        {
            this.VirtualMachine = new Lua();
            this.functions = new Dictionary<String, LuaFunctionAttribute>();
            this.scripts = new Dictionary<string, LuaScript>();

            this.RegisterObject(this);
        }

        /// <summary>
        /// Construtor utilizando de objeto a ser registrado.
        /// </summary>
        /// <param name="targetObject">Objeto a ser registrado.</param>
        public LuaVirtualMachine(object targetObject)
        {
            this.VirtualMachine = new Lua();
            this.functions = new Dictionary<String, LuaFunctionAttribute>();
            this.scripts = new Dictionary<string, LuaScript>();

            RegisterObject(targetObject);

            this.RegisterObject(this);
        }

        /// <summary>
        /// Construtor utilizando de objeto a ser registrado.
        /// </summary>
        /// <param name="targetObject">Objeto a ser registrado.</param>
        /// <param name="ID">ID do Objeto, deve ser unico.</param>
        public LuaVirtualMachine(object targetObject, string ID)
        {
            this.VirtualMachine = new Lua();
            this.functions = new Dictionary<String, LuaFunctionAttribute>();
            this.scripts = new Dictionary<string, LuaScript>();

            RegisterObject(targetObject, ID);

            this.RegisterObject(this);
        }

        /// <summary>
        /// Registrar metodos do objeto na LuaVM.
        /// </summary>
        /// <param name="targetObject">Objeto a ser registrado.</param>
        /// <param name="ID">ID do Objeto, deve ser unico.</param>
        public void RegisterObject(object targetObject, string ID = "")
        {
            //Para cada método.
            foreach (MethodInfo methodInfo in targetObject.GetType().GetMethods())
            {
                //Para cada atributo do método.
                foreach (Attribute attribute in Attribute.GetCustomAttributes(methodInfo))
                {
                    //Se atributo for o LuaFunctionAttr.
                    if (attribute.GetType() == typeof(LuaFunctionAttribute))
                    {
                        //Transforme no nosso atributo.
                        LuaFunctionAttribute methodLuaAttribute = attribute as LuaFunctionAttribute;

                        //Nome, descrição e parametros.
                        String functionName = methodLuaAttribute.Name;

                        //Definir ID no nome do método.
                        if (ID != null && ID.Length != 0)
                        {
                            methodLuaAttribute.Name = ID + "_" + methodLuaAttribute.Name;
                            functionName = ID + "_" + functionName;
                        }

                        String functionDescription = methodLuaAttribute.Description;
                        String[] functionParametersDescription = methodLuaAttribute.Parameters;

                        //Parametros do método.
                        ParameterInfo[] parametersInfo = methodInfo.GetParameters();

                        //Validar numero de parametros descritos com numero de parametros existentes.
                        if (functionParametersDescription != null && (parametersInfo.Length != functionParametersDescription.Length))
                        {
                            Console.WriteLine("Função " + methodInfo.Name + " (descrita como " +
                                                              functionName + ") possui erro na descrição dos argumentos. Descrito " +
                                                              functionParametersDescription.Length + " parametro(s) mas requer " +
                                                              parametersInfo.Length + " descrição(ões).");
                            break;
                        }
                        else if (functionParametersDescription == null && (parametersInfo.Length != 0))
                        {
                            Console.WriteLine("Função " + methodInfo.Name + " (descrita como " +
                                                                  functionName + ") possui erro na descrição dos argumentos. Descrito nenhum parametro(s) mas requer " +
                                                                  parametersInfo.Length + " descrição(ões).");
                        }
                        else
                        {
                            //Criar descrição do tipo dos parametros.
                            for (int i = 0; i < parametersInfo.Length; i++)
                            {
                                functionParametersDescription[i] += "(" + parametersInfo[i].ParameterType.ToString() + ")";
                            }
                        }

                        // Adicionar a função ao Hashtable.
                        functions.Add(functionName, methodLuaAttribute);

                        //Registrar função na Virtual Machine do Lua.
                        virtualMachine.RegisterFunction(functionName, targetObject, methodInfo);
                    }
                }
            }
        }

        /// <summary>
        /// Retornar lista de funções registradas nesta Virtual Machine.
        /// </summary>
        /// <param name="debug">Imprimir descrição do método.</param>
        /// <returns>Lista de funções.</returns>
        public String[] RegistredFunctions(bool debug = false)
        {
            List<String> functions = new List<string>();

            foreach (KeyValuePair<String,LuaFunctionAttribute> function in Functions)
            {
                LuaFunctionAttribute methodAttribute = function.Value as LuaFunctionAttribute;

                //Delimitador.
                String description = "---" + System.Environment.NewLine;

                //Função e descrição.
                description += "Function: " + methodAttribute.Name + System.Environment.NewLine;
                description += "Description: " + methodAttribute.Description;

                //Metodo possui parametros
                if (methodAttribute.Parameters != null &&
                    methodAttribute.Parameters.Length > 0)
                {
                    //Descrever cada parametro.
                    for (int i = 0; i < methodAttribute.Parameters.Count(); i++)
                    {
                        description += System.Environment.NewLine;
                        description += "Parameter(" + i + "): " + methodAttribute.Parameters[i];
                    }
                }
                else
                {
                    //Sem parametros.
                    description += System.Environment.NewLine;
                    description += "No parameters.";
                }

                //Delimitador.
                description += System.Environment.NewLine + "---";

                //Se modo debug, jogar na tela.
                if (debug) Console.WriteLine(description);

                functions.Add(description);
            }

            return functions.ToArray();
        }

        /// <summary>
        /// Retorna métodos registrados da Virtual Machine.
        /// </summary>
        /// <param name="debug">Modo debug.</param>
        /// <returns></returns>
        [LuaFunctionAttribute("Methods", "Lista e descreve metodos registrados na Virtual Machine.", "Modo Debug.")]
        public String[] Methods(bool debug = false)
        {
            return this.RegistredFunctions(debug);
        }

        /// <summary>
        /// Retorna métodos registrados da Virtual Machine como uma unica string.
        /// </summary>
        /// <param name="debug">Modo debug.</param>
        /// <returns></returns>
        [LuaFunctionAttribute("Methods_String", "Lista e descreve methodos registrados na Virtual Machine em uma unica string..", "Modo Debug.")]
        public String Methods_String(bool debug = false)
        {
            String returns = "";
            String[] methods = this.RegistredFunctions(debug);

            for (int i = 0; i < methods.Length; i++)
            {
                returns += methods[i];
            }

            return returns;
        }

        /// <summary>
        /// Criar Vector2 com os parametros. Criado com objetivo maior o Lua.
        /// </summary>
        /// <param name="x">Valor de X.</param>
        /// <param name="y">Valor de Y</param>
        /// <returns></returns>
        [LuaFunctionAttribute("Vector2", "Criar um Vector2 com parametros.", "Valor de X.", "Valor de Y.")]
        public Vector2 Vector2(float x, float y)
        {
            return new Vector2(x, y);
        }

        /// <summary>
        /// Criar Color com os parametros. Criado com objetivo maior o Lua.
        /// </summary>
        /// <param name="r">Tom de vermelho.</param>
        /// <param name="g">Tom de verde.</param>
        /// <param name="b">Tom de azul.</param>
        /// <param name="a">Escala de alpha.</param>
        /// <returns></returns>
        [LuaFunctionAttribute("Color", "Criar Color com os parametros.", "Valor de Vermelho(R).", "Valor de Verde(G).", "Valor de Azul(B).", "Valor de Alpha(A).")]
        public Color Color(int r = 255, int g = 255, int b = 255, int a = 255)
        {
            return new Color(r, g, b, a);
        }

        /// <summary>
        /// Executar script manualmente.
        /// </summary>
        /// <param name="script">Script a ser executado.</param>
        /// <returns>Retorna se ocorreu tudo como esperado.</returns>
        public object[] Run(String script)
        {
            if (script != null && script.Length > 0)
            {
                try
                {
                    return virtualMachine.DoString(script);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return new object[] { ex.Message };
                }
            }

            return null;
        }

        /// <summary>
        /// Executar script salvo em Scripts.
        /// </summary>
        /// <param name="scriptName">Nome/Chave do script.</param>
        /// <param name="variables">Pode ser usado para definir o valor da variavel de script.</param>
        /// <returns></returns>
        public object[] Execute(String script, params object[] variables)
        {
            try
            {
                return Scripts[script].Execute(this.virtualMachine, variables);
            }
            catch
            {
                return null;
            }
        }

    }
}
